using System;
using System.Globalization;
using System.Text;
using System.Collections;
using System.Collections.Specialized;
using System.Threading;
using System.Reflection;
using System.Security.Cryptography;
using System.Collections.Generic;
using AsterNET.Manager.Event;
using AsterNET.Manager.Response;
using AsterNET.Manager;

namespace AsterNET
{
	internal class Helper
	{
		private static CultureInfo defaultCulture;
#if LOGGER
		private static Logger logger = Logger.Instance();
#endif

		#region CultureInfo 
		internal static CultureInfo CultureInfo
		{
			get
			{
				if (defaultCulture == null)
					defaultCulture = System.Globalization.CultureInfo.GetCultureInfo("en");
				return defaultCulture;
			}
		}
		#endregion

		#region ToHexString(sbyte[]) 
		/// <summary> The hex digits used to build a hex string representation of a byte array.</summary>
		internal static readonly char[] hexChar = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };

		/// <summary>
		/// Converts a byte array to a hex string representing it. The hex digits are lower case.
		/// </summary>
		/// <param name="b">the byte array to convert</param>
		/// <returns> the hex representation of b</returns>
		internal static string ToHexString(sbyte[] b)
		{
			StringBuilder sb = new StringBuilder(b.Length * 2);
			for (int i = 0; i < b.Length; i++)
			{
				sb.Append(hexChar[Helper.URShift((b[i] & 0xf0), 4)]);
				sb.Append(hexChar[b[i] & 0x0f]);
			}
			return sb.ToString();
		}
		#endregion

		#region GetInternalActionId(actionId) 
		internal static string GetInternalActionId(string actionId)
		{
			if (string.IsNullOrEmpty(actionId))
				return string.Empty;
			int delimiterIndex = actionId.IndexOf(Common.INTERNAL_ACTION_ID_DELIMITER);
			if (delimiterIndex > 0)
				return actionId.Substring(0, delimiterIndex).Trim();
			return string.Empty;
		}
		#endregion

		#region StripInternalActionId(actionId) 
		internal static string StripInternalActionId(string actionId)
		{
			if (string.IsNullOrEmpty(actionId))
				return string.Empty;
			int delimiterIndex = actionId.IndexOf(Common.INTERNAL_ACTION_ID_DELIMITER);
			if (delimiterIndex > 0)
			{
                if (actionId.Length > delimiterIndex + 1)
                    return actionId.Substring(delimiterIndex + 1).Trim();
                else
                    return actionId.Substring(0, delimiterIndex).Trim();
			}
			return string.Empty;
		}
		#endregion

		#region IsTrue(string) 
		/// <summary>
		/// Checks if a String represents <code>true</code> or <code>false</code> according to Asterisk's logic.<br/>
		/// The original implementation is <code>util.c</code> is as follows:
		/// </summary>
		/// <param name="s">the String to check for <code>true</code>.</param>
		/// <returns>
		/// <code>true</code> if s represents <code>true</code>,
		/// <code>false</code> otherwise.
		/// </returns>
		internal static bool IsTrue(string s)
		{
			if (s == null || s.Length == 0)
				return false;
			string sx = s.ToLower(Helper.CultureInfo);
			if (sx == "yes" || sx == "true" || sx == "y" || sx == "t" || sx == "1" || sx == "on")
				return true;
			return false;
		}
		#endregion

		#region URShift(...) 
		/// <summary>
		/// Performs an unsigned bitwise right shift with the specified number
		/// </summary>
		/// <param name="number">Number to operate on</param>
		/// <param name="bits">Ammount of bits to shift</param>
		/// <returns>The resulting number from the shift operation</returns>
		internal static int URShift(int number, int bits)
		{
			if (number >= 0)
				return number >> bits;
			else
				return (number >> bits) + (2 << ~bits);
		}

		/// <summary>
		/// Performs an unsigned bitwise right shift with the specified number
		/// </summary>
		/// <param name="number">Number to operate on</param>
		/// <param name="bits">Ammount of bits to shift</param>
		/// <returns>The resulting number from the shift operation</returns>
		internal static int URShift(int number, long bits)
		{
			return URShift(number, (int)bits);
		}

		/// <summary>
		/// Performs an unsigned bitwise right shift with the specified number
		/// </summary>
		/// <param name="number">Number to operate on</param>
		/// <param name="bits">Ammount of bits to shift</param>
		/// <returns>The resulting number from the shift operation</returns>
		internal static long URShift(long number, int bits)
		{
			if (number >= 0)
				return number >> bits;
			else
				return (number >> bits) + (2L << ~bits);
		}

		/// <summary>
		/// Performs an unsigned bitwise right shift with the specified number
		/// </summary>
		/// <param name="number">Number to operate on</param>
		/// <param name="bits">Ammount of bits to shift</param>
		/// <returns>The resulting number from the shift operation</returns>
		internal static long URShift(long number, long bits)
		{
			return URShift(number, (int)bits);
		}
		#endregion

		#region ToArray(ICollection c, object[] objects) 
		/// <summary>
		/// Obtains an array containing all the elements of the collection.
		/// </summary>
		/// <param name="objects">The array into which the elements of the collection will be stored.</param>
		/// <returns>The array containing all the elements of the collection.</returns>
		internal static object[] ToArray(ICollection c, object[] objects)
		{
			int index = 0;

			Type type = objects.GetType().GetElementType();
			object[] objs = (object[])Array.CreateInstance(type, c.Count);

			IEnumerator e = c.GetEnumerator();

			while (e.MoveNext())
				objs[index++] = e.Current;

			//If objects is smaller than c then do not return the new array in the parameter
			if (objects.Length >= c.Count)
				objs.CopyTo(objects, 0);

			return objs;
		}
		#endregion

		#region ParseVariables(Dictionary<string, string> dictionary, string variables, char[] delim)
		/// <summary>
		/// Parse variable(s) string to dictionary.
		/// </summary>
		/// <param name="dictionary"></param>
		/// <param name="variables">variable(a) string</param>
		/// <param name="delim">variable pairs delimiter</param>
		/// <returns></returns>
		internal static Dictionary<string, string> ParseVariables(Dictionary<string, string> dictionary, string variables, char[] delim)
		{
			if (dictionary == null)
				dictionary = new Dictionary<string, string>();
			else
				dictionary.Clear();

			if (string.IsNullOrEmpty(variables))
				return dictionary;
			string[] vars = variables.Split(delim);
			int idx;
			string vname, vval;
			foreach (string var in vars)
			{
				idx = var.IndexOf('=');
				if (idx > 0)
				{
					vname = var.Substring(0, idx);
					vval = var.Substring(idx + 1);
				}
				else
				{
					vname = var;
					vval = string.Empty;
				}
				dictionary.Add(vname, vval);
			}
			return dictionary;
		}
		#endregion

		#region JoinVariables(IDictionary dictionary, string delim) 
		/// <summary>
		/// Join variables dictionary to string.
		/// </summary>
		/// <param name="dictionary"></param>
		/// <param name="delim"></param>
		/// <returns></returns>
		internal static string JoinVariables(IDictionary dictionary, char[] delim, string delimKeyValue)
		{
			return JoinVariables(dictionary, new string(delim), delimKeyValue);
		}
		internal static string JoinVariables(IDictionary dictionary, string delim, string delimKeyValue)
		{
			if (dictionary == null)
				return string.Empty;
			StringBuilder sb = new StringBuilder();
			foreach (DictionaryEntry var in dictionary)
			{
				if (sb.Length > 0)
					sb.Append(delim);
				sb.Append(string.Concat(var.Key, delimKeyValue, var.Value));
			}
			return sb.ToString();
		}
		#endregion

		#region GetMillisecondsFrom(DateTime start) 
		internal static long GetMillisecondsFrom(DateTime start)
		{
			TimeSpan ts = (TimeSpan)(DateTime.Now - start);
			return (long)ts.TotalMilliseconds;
		}
		#endregion

		#region ParseString(string val) 
		internal static object ParseString(string val)
		{
			if (val == "none")
				return string.Empty;
			return val;
		}
		#endregion

		#region GetGetters(class) 
		/// <summary>
		/// Returns a Map of getter methods of the given class.<br/>
		/// The key of the map contains the name of the attribute that can be accessed by the getter, the
		/// value the getter itself . A method is considered a getter if its name starts with "get",
		/// it is declared internal and takes no arguments.
		/// </summary>
		/// <param name="clazz">the class to return the getters for</param>
		/// <returns> a Map of attributes and their accessor methods (getters)</returns>
		internal static Dictionary<string, MethodInfo> GetGetters(Type clazz)
		{
			string name;
			string methodName;
			MethodInfo method;

			Dictionary<string, MethodInfo> accessors = new Dictionary<string, MethodInfo>();
			MethodInfo[] methods = clazz.GetMethods();

			for (int i = 0; i < methods.Length; i++)
			{
				method = methods[i];
				methodName = method.Name;

				// skip not "get..." methods and  skip methods with != 0 parameters
				if (!methodName.StartsWith("get_") || method.GetParameters().Length != 0)
					continue;

				name = methodName.Substring(4);
				if (name.Length == 0)
					continue;
				accessors[name] = method;
			}
			return accessors;
		}
		#endregion

		#region GetSetters(Type clazz) 
		/// <summary>
		/// Returns a Map of setter methods of the given class.<br/>
		/// The key of the map contains the name of the attribute that can be accessed by the setter, the
		/// value the setter itself. A method is considered a setter if its name starts with "set",
		/// it is declared internal and takes no arguments.
		/// </summary>
		/// <param name="clazz">the class to return the setters for</param>
		/// <returns> a Map of attributes and their accessor methods (setters)</returns>
		internal static IDictionary GetSetters(Type clazz)
		{
			IDictionary accessors = new Hashtable();
			MethodInfo[] methods = clazz.GetMethods();
			string name;
			string methodName;
			MethodInfo method;

			for (int i = 0; i < methods.Length; i++)
			{
				method = methods[i];
				methodName = method.Name;
				// skip not "set..." methods and  skip methods with != 1 parameters
				if (!methodName.StartsWith("set_") || method.GetParameters().Length != 1)
					continue;
				name = methodName.Substring("set_".Length).ToLower(Helper.CultureInfo);
				if (name.Length == 0) continue;
				accessors[name] = method;
			}
			return accessors;
		}
		#endregion

		#region ToString(object obj) 
		/// <summary>
		/// Convert object with all properties to string
		/// </summary>
		/// <param name="obj"></param>
		/// <returns></returns>
		internal static string ToString(object obj)
		{
			object value;
			StringBuilder sb = new StringBuilder(obj.GetType().Name, 1024);
			sb.Append(" {");
			string strValue;
			IDictionary getters = Helper.GetGetters(obj.GetType());
			bool notFirst = false;
			List<MethodInfo> arrays = new List<MethodInfo>();
			// First step - all values properties (not a list)
			foreach (string name in getters.Keys)
			{
				MethodInfo getter = (MethodInfo)getters[name];
				Type propType = getter.ReturnType;
				if (propType == typeof(object))
					continue;
				if (!(propType == typeof(string) || propType == typeof(bool) || propType == typeof(double) || propType == typeof(DateTime) || propType == typeof(int) || propType == typeof(long)))
				{
					string propTypeName = propType.Name;
					if (propTypeName.StartsWith("Dictionary") || propTypeName.StartsWith("List"))
					{
						arrays.Add(getter);
						continue;
					}
					continue;
				}

				try
				{
					value = getter.Invoke(obj, new object[] { });
				}
				catch
				{
					continue;
				}

				if (value == null)
					continue;
				if (value is string)
				{
					strValue = (string)value;
					if (strValue.Length == 0)
						continue;
				}
				else if (value is bool)
				{
					strValue = ((bool)value ? "true" : "false");
				}
				else if (value is double)
				{
					double d = (double)value;
					if (d == 0.0)
						continue;
					strValue = d.ToString();
				}
				else if (value is DateTime)
				{
					DateTime dt = (DateTime)value;
					if (dt == DateTime.MinValue)
						continue;
					strValue = dt.ToLongTimeString();
				}
				else if (value is int)
				{
					int i = (int)value;
					if (i == 0)
						continue;
					strValue = i.ToString();
				}
				else if (value is long)
				{
					long l = (long)value;
					if (l == 0)
						continue;
					strValue = l.ToString();
				}
				else
					strValue = value.ToString();

				if (notFirst)
					sb.Append("; ");
				notFirst = true;
				sb.Append(string.Concat(getter.Name.Substring(4), ":", strValue));
			}

			// Second step - all lists
			foreach (MethodInfo getter in arrays)
			{
				value = null;
				try
				{
					value = getter.Invoke(obj, new object[] { });
				}
				catch
				{
					continue;
				}
				if (value == null)
					continue;

				#region List 
				IList list;
				if (value is IList && (list = (IList)value).Count > 0)
				{
					if (notFirst)
						sb.Append("; ");
					notFirst = true;
					sb.Append(getter.Name.Substring(4));
					sb.Append(":[");
					bool notFirst2 = false;
					foreach (object o in list)
					{
						if (notFirst2)
							sb.Append("; ");
						notFirst2 = true;
						sb.Append(o.ToString());
					}
					sb.Append("]");
					continue;
				}
				#endregion

				#region IDictionary 
				else if (value is IDictionary && ((IDictionary)value).Count > 0)
				{
					if (notFirst)
						sb.Append("; ");
					notFirst = true;
					sb.Append(getter.Name.Substring(4));
					sb.Append(":[");
					bool notFirst2 = false;
					foreach (object key in ((IDictionary)value).Keys)
					{
						object o = ((IDictionary)value)[key];
						if (notFirst2)
							sb.Append("; ");
						notFirst2 = true;
						sb.Append(string.Concat(key, ":", o));
					}
					sb.Append("]");
					continue;
				}
				#endregion
			}

			sb.Append("}");
			return sb.ToString();
		}
		#endregion

		#region SetAttributes(object evt, IDictionary attributes) 
		internal static void SetAttributes(IParseSupport o, Dictionary<string, string> attributes)
		{
			Type dataType;
			object val;

			// Preparse attributes
			attributes = o.ParseSpecial(attributes);

			IDictionary setters = Helper.GetSetters(o.GetType());
			MethodInfo setter;
			foreach (string name in attributes.Keys)
			{
				if (name == "event")
					continue;

				if (name == "source")
					setter = (MethodInfo)setters["src"];
				else
					setter = (MethodInfo)setters[stripIllegalCharacters(name)];

				if (setter == null)
				{
					// No setter found to key, try general parser
					if (!o.Parse(name, (string)attributes[name]))
					{
#if LOGGER
						logger.Error("Unable to set property '" + name + "' on " + o.GetType() + ": no setter");
#endif
						throw new ManagerException("Parse error key '" + name + "' on " + o.GetType());
					}
				}
				else
				{
					dataType = (setter.GetParameters()[0]).ParameterType;
					if (dataType == typeof(bool))
						val = Helper.IsTrue((string)attributes[name]);
					else if (dataType == typeof(string))
						val = Helper.ParseString((string)attributes[name]);
					else if (dataType == typeof(Int32))
					{
						Int32 v = 0;
						Int32.TryParse((string)attributes[name], out v);
						val = v;
					}
					else if (dataType == typeof(Int64))
					{
						Int64 v = 0;
						Int64.TryParse((string)attributes[name], out v);
						val = v;
					}
					else if (dataType == typeof(double))
					{
						Double v = 0.0;
						Double.TryParse((string)attributes[name], System.Globalization.NumberStyles.AllowDecimalPoint, Common.CultureInfoEn, out v);
						val = v;
					}
					else if (dataType == typeof(decimal))
					{
						Decimal v = 0;
						Decimal.TryParse((string)attributes[name], System.Globalization.NumberStyles.AllowDecimalPoint, Common.CultureInfoEn, out v);
						val = v;
					}
					else if (dataType.IsEnum)
					{
						try
						{
							val = Convert.ChangeType(Enum.Parse(dataType, (string)attributes[name], true), dataType);
						}
						catch (Exception ex)
						{
#if LOGGER
							logger.Error("Unable to convert value '" + attributes[name] + "' of property '" + name + "' on " + o.GetType() + " to required enum type " + dataType, ex);
							continue;
#else
							throw new ManagerException("Unable to convert value '" + attributes[name] + "' of property '" + name + "' on " + o.GetType() + " to required enum type " + dataType, ex); 
#endif
						}
					}
					else
					{
						try
						{
							ConstructorInfo constructor = dataType.GetConstructor(new Type[] { typeof(string) });
							val = constructor.Invoke(new object[] { attributes[name] });
						}
						catch (Exception ex)
						{
#if LOGGER
							logger.Error("Unable to convert value '" + attributes[name] + "' of property '" + name + "' on " + o.GetType() + " to required type " + dataType, ex);
							continue;
#else
							throw new ManagerException("Unable to convert value '" + attributes[name] + "' of property '" + name + "' on " + o.GetType() + " to required type " + dataType, ex);
#endif
						}
					}

					try
					{
						setter.Invoke(o, new object[] { val });
					}
					catch (Exception ex)
					{
#if LOGGER
						logger.Error("Unable to set property '" + name + "' on " + o.GetType(), ex);
						continue;
#else
						throw new ManagerException("Unable to set property '" + name + "' on " + o.GetType(), ex);
#endif
					}
				}
			}
		}
		#endregion

		#region AddKeyValue(IDictionary list, string line) 
		internal static void AddKeyValue(IDictionary list, string line)
		{
			int delimiterIndex = line.IndexOf(":");
			if (delimiterIndex > 0 && line.Length > delimiterIndex + 1)
			{
				string name = line.Substring(0, delimiterIndex).ToLower(Helper.CultureInfo).Trim();
				string val = line.Substring(delimiterIndex + 1).Trim();
				if (val == "<null>")
					list[name] = null;
				else
					list[name] = val;
			}
		}
		#endregion

		#region stripIllegalCharacters(string s) 
		/// <summary>
		/// Strips all illegal charaters from the given lower case string.
		/// </summary>
		/// <param name="s">the original string</param>
		/// <returns>the string with all illegal characters stripped</returns>
		private static string stripIllegalCharacters(string s)
		{
			char c;
			bool needsStrip = false;

			if (string.IsNullOrEmpty(s))
				return null;

			for (int i = 0; i < s.Length; i++)
			{
				c = s[i];
				if (c >= '0' && c <= '9')
					continue;
				else if (c >= 'a' && c <= 'z')
					continue;
				else if (c >= 'A' && c <= 'Z')
					continue;
				else
				{
					needsStrip = true;
					break;
				}
			}

			if (!needsStrip)
				return s;

			StringBuilder sb = new StringBuilder(s.Length);
			for (int i = 0; i < s.Length; i++)
			{
				c = s[i];
				if (c >= '0' && c <= '9')
					sb.Append(c);
				else if (c >= 'a' && c <= 'z')
					sb.Append(c);
				else if (c >= 'A' && c <= 'Z')
					sb.Append(c);
			}

			return sb.ToString();
		}
		#endregion

		#region BuildResponse(IDictionary attributes) 
		/// <summary>
		/// Constructs an instance of ManagerResponse based on a map of attributes.
		/// </summary>
		/// <param name="attributes">the attributes and their values. The keys of this map must be all lower case.</param>
		/// <returns>the response with the given attributes.</returns>
		internal static ManagerResponse BuildResponse(Dictionary<string, string> attributes)
		{
			ManagerResponse response;

			string responseType = ((string)attributes["response"]).ToLower(Helper.CultureInfo);

			// Determine type
			if (responseType == "error")
				response = new ManagerError();
			else if (attributes.ContainsKey("challenge"))
				response = new ChallengeResponse();
			else if (attributes.ContainsKey("mailbox") && attributes.ContainsKey("waiting"))
				response = new MailboxStatusResponse();
			else if (attributes.ContainsKey("mailbox") && attributes.ContainsKey("newmessages") && attributes.ContainsKey("oldmessages"))
				response = new MailboxCountResponse();
			else if (attributes.ContainsKey("exten") && attributes.ContainsKey("context") && attributes.ContainsKey("hint") && attributes.ContainsKey("status"))
				response = new ExtensionStateResponse();
			else
				response = new ManagerResponse();

			Helper.SetAttributes(response, attributes);
			return response;
		}
		#endregion

		#region BuildEvent(Hashtable list, object source, IDictionary attributes) 
		/// <summary>
		/// Builds the event based on the given map of attributes and the registered event classes.
		/// </summary>
		/// <param name="source">source attribute for the event</param>
		/// <param name="attributes">map containing event attributes</param>
		/// <returns>a concrete instance of ManagerEvent or <code>null</code> if no event class was registered for the event type.</returns>
		internal static ManagerEvent BuildEvent(IDictionary<int, ConstructorInfo> list, ManagerConnection source, Dictionary<string, string> attributes)
		{
			ManagerEvent e;
			string eventType;
			ConstructorInfo constructor = null;
			int hash, hashEvent;

			eventType = ((string)attributes["event"]).ToLower(Helper.CultureInfo);
			// Remove Event tail from event name (ex. JabberEvent)
			if (eventType.EndsWith("event"))
				eventType = eventType.Substring(0, eventType.Length - 5);
			hashEvent = eventType.GetHashCode();

			if (eventType == "user")
			{
				string userevent = ((string)attributes["userevent"]).ToLower(Helper.CultureInfo);
				hash = string.Concat(eventType, userevent).GetHashCode();
				if(list.ContainsKey(hash))
					constructor = list[hash];
				else
					constructor = list[hashEvent];
			}
			else if (list.ContainsKey(hashEvent))
				constructor = list[hashEvent];

			if (constructor == null)
				e = new UnknownEvent(source);
			else
			{
				try
				{
					e = (ManagerEvent)constructor.Invoke(new object[] { source });
				}
				catch (Exception ex)
				{
#if LOGGER
					logger.Error("Unable to create new instance of " + eventType, ex);
					return null;
#else
					throw ex;
#endif
				}
			}

			SetAttributes(e, attributes);

			// ResponseEvents are sent in response to a ManagerAction if the
			// response contains lots of data. They include the actionId of
			// the corresponding ManagerAction.
			if (e is ResponseEvent)
			{
				ResponseEvent responseEvent = (ResponseEvent)e;
				string actionId = responseEvent.ActionId;
				if (actionId != null)
				{
					responseEvent.ActionId = Helper.StripInternalActionId(actionId);
					responseEvent.InternalActionId = Helper.GetInternalActionId(actionId);
				}
			}

			return e;
		}
		#endregion

		#region RegisterBuiltinEventClasses(Hashtable list) 
		/// <summary>
		/// Register buildin Event classes
		/// </summary>
		/// <param name="list"></param>
		internal static void RegisterBuiltinEventClasses(Dictionary<int, ConstructorInfo> list)
		{
			Assembly assembly = Assembly.GetExecutingAssembly();
			Type manager = typeof(ManagerEvent);
			foreach (Type type in assembly.GetTypes())
				if (type.IsPublic && !type.IsAbstract && manager.IsAssignableFrom(type))
					RegisterEventClass(list, type);
		}
		#endregion

		#region RegisterEventClass(Dictionary<string, ConstructorInfo> list, Type clazz)
		internal static void RegisterEventClass(Dictionary<int, ConstructorInfo> list, Type clazz)
		{
			// Ignore all abstract classes
			// Class not derived from ManagerEvent
			if (clazz.IsAbstract || !typeof(ManagerEvent).IsAssignableFrom(clazz))
				return;

			string eventType = clazz.Name.ToLower(Helper.CultureInfo);

			// Remove "event" at the end (if presents)
			if (eventType.EndsWith("event"))
				eventType = eventType.Substring(0, eventType.Length - 5);

			// If assignable from UserEvent and no "userevent" at the start - add "userevent" to beginning
			if (typeof(UserEvent).IsAssignableFrom(clazz) && !eventType.StartsWith("user"))
				eventType = "user" + eventType;

			int hash = eventType.GetHashCode();
			if (list.ContainsKey(hash))
				return;

			ConstructorInfo constructor = null;
			try
			{
				constructor = clazz.GetConstructor(new Type[] { typeof(ManagerConnection) });
			}
			catch (MethodAccessException ex)
			{
				throw new ArgumentException("RegisterEventClass : " + clazz + " has no usable constructor.", ex);
			}

			if (constructor != null && constructor.IsPublic)
				list.Add(hash, constructor);
			else
				throw new ArgumentException("RegisterEventClass : " + clazz + " has no public default constructor");
		}
		#endregion

		#region RegisterEventHandler(Dictionary<int, int> list, int index, Type eventType) 
		internal static void RegisterEventHandler(Dictionary<int, int> list, int index, Type eventType)
		{
			int eventHash = eventType.Name.GetHashCode();
			if (list.ContainsKey(eventHash))
				throw new ArgumentException("Event class already registered : " + eventType.Name);
			list.Add(eventHash, index);
		}
		#endregion
	}
}